#include <jni.h>
#include <string>
#include <android/log.h>

#define TAG "native-lib"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)

extern "C" JNIEXPORT jstring JNICALL
Java_com_nelson_android_ndk_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}


extern "C"
JNIEXPORT jstring JNICALL
Java_com_nelson_android_ndk_MainActivity_operateString(JNIEnv *env, jobject thiz, jstring str) {
    // 从 Java 内存中把字符串拷贝出来，在 native 使用
    const char *strFromJava = env->GetStringUTFChars(str, NULL);
    // NPE check
    if (strFromJava == NULL) {
        return NULL;
    }

    // 将 strFromJava 拷贝到 buff 中，待会儿好拿去生成字符串
    char buff[128] = {0};
    strcpy(buff, strFromJava);
    strcat(buff, " 在字符串后面加点东西");

    // 释放资源
    env->ReleaseStringUTFChars(str, strFromJava);
    // 自动转成 Unicode
    return env->NewStringUTF(buff);
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_nelson_android_ndk_MainActivity_sumArray(JNIEnv *env, jobject thiz, jintArray array) {
    // 数据求和
    int result = 0;

    // 方式1 推荐使用
    jint arr_len = env->GetArrayLength(array);

    // 动态申请数组
    jint *c_array = static_cast<jint *>(malloc(sizeof(jint) * arr_len));

    // 初始化数组元素内容为 0
    memset(c_array, 0, sizeof(jint) * arr_len);

    // 将 Java 数组的 [0-arr_len] 位置的元素拷贝到 c_array 数组中
    env->GetIntArrayRegion(array, 0, arr_len, c_array);
    for (int i = 0; i < arr_len; ++i) {
        result += c_array[i];
    }

    // 动态申请的内存 必须释放
    free(c_array);
    return result;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_nelson_android_ndk_MainActivity_sumArray2(JNIEnv *env, jobject thiz, jintArray array) {
    // 数据求和
    // 方式2 不推荐使用
    int result = 0;

    // 此种方式比较危险，GetIntArrayElements 会直接获取数组元素指针，是可以直接对该数组元素进行修改的。
    jint *c_arr = env->GetIntArrayElements(array, NULL);
    if (c_arr == NULL) {
        return 0;
    }

    c_arr[0] = 15;
    jint len = env->GetArrayLength(array);
    for (int i = 0; i < len; ++i) {
        //result += *(c_arr + i); 写成这种形式，或者下面一行那种都行
        result += c_arr[i];
    }
    // 有 Get, 一般就有 release
    env->ReleaseIntArrayElements(array, c_arr, 0);
    return result;
}

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_nelson_android_ndk_MainActivity_init2DArray(JNIEnv *env, jobject thiz, jint size) {
    // 创建一个 size * size 大小的二维数组
    // jobjectArray 是用来装对象数组的   Java 数组就是一个对象 int[]
    jclass classIntArray = env->FindClass("[I");
    if (classIntArray == NULL) {
        return NULL;
    }
    jobjectArray result = env->NewObjectArray(size, classIntArray, NULL);
    for (int i = 0; i < size; ++i) {
        jint buff[100];
        // 创建第二维数组，是第一维数组的一个元素
        jintArray intArr = env->NewIntArray(size);
        if (intArr == NULL) {
            return NULL;
        }

        for (int j = 0; j < size; ++j) {
            buff[j] = 666;
        }
        // 给一个 jintArray 设置数据
        env->SetIntArrayRegion(intArr, 0, size, buff);
        // 给一个 jobjectArray 设置数据 第 i 索引，数据位 intArr
        env->SetObjectArrayElement(result, i, intArr);
        // 及时移除引用
        env->DeleteLocalRef(intArr);
    }
    return result;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_nelson_android_ndk_MainActivity_callJavaStaticMethod(JNIEnv *env, jobject thiz) {
    // 调用某个类的static方法
    // 1.从 classpath 路径下搜索 MyJNIClass 这个类,并返回该类的 Class 对象
    jclass clazz = env->FindClass("com/nelson/android/ndk/MyJNIClass");
    // 2.从 clazz 类中查找 getDes 方法 得到这个静态方法的方法 id
    jmethodID mid_get_des = env->GetStaticMethodID(clazz, "getDes", "(Ljava/lang/String;)Ljava/lang/String;");
    // 3.构建入参,调用 static 方法,获取返回值
    jstring str_arg = env->NewStringUTF("我是 Nelson");
    jstring result = static_cast<jstring>(env->CallStaticObjectMethod(clazz, mid_get_des, str_arg));
    const char *result_str = env->GetStringUTFChars(result, NULL);
    LOGV("获取到 Java 层返回的数据：%s", result_str);

    // 4.移除局部引用
    env->DeleteLocalRef(clazz);
    env->DeleteLocalRef(str_arg);
    env->DeleteLocalRef(result);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_nelson_android_ndk_MainActivity_callJavaInstanceMethod(JNIEnv *env, jobject thiz) {
    jclass clazz = env->FindClass("com/nelson/android/ndk/MyJNIClass");
    // 获取构造方法的方法 id
    jmethodID mid_construct = env->GetMethodID(clazz, "<init>", "()V");
    // 获取 getAge 方法的方法 id
    jmethodID mid_get_age = env->GetMethodID(clazz, "getAge", "()I");
    jmethodID mid_set_age = env->GetMethodID(clazz, "setAge", "(I)V");
    jobject jobj = env->NewObject(clazz, mid_construct);

    env->NewObject(clazz, mid_construct);

//    jclass clazzA = env->FindClass("com/nelson/android/ndk/TestA");
//    jmethodID constructA = env->GetMethodID(clazzA, "<init>", "()V");
//    env->NewObject(clazzA, constructA);


    // 调用方法 setAge
//    env->CallVoidMethod(jobj, mid_set_age, 20);
    // 再调用方法 getAge 获取返回值 打印输出
//    jint age = env->CallIntMethod(jobj, mid_get_age);
//    LOGV("获取到 age = %d", age);

    // 凡是使用是 jobject 的子类,都需要移除引用
    env->DeleteLocalRef(clazz);
    env->DeleteLocalRef(jobj);
}

// 借助 addr2line 和 ndk-stack 两款工具来协助我们进行分析错误的堆栈，可以清楚看到时哪行出错了。
// 1. ndk toolchains-add2line
// aarch64-linux-android-addr2line -e xx.so 0000000000010e60 0000000000010e38
// -e 是指定 so 文件的位置，末尾的 0000000000010e60 是错误位置的汇编指令地址

// 2. ndk-ndk-stack
// adb logcat | ndk-stack -sym /app/build/intermediates/cmake/debug/obj/arm64-v8a
void willCrash() {
    JNIEnv *env = NULL;
    int version = env->GetVersion();
}

extern "C"
JNIEXPORT void JNICALL
Java_com_nelson_android_ndk_MainActivity_nativeCrashTest(JNIEnv *env, jobject thiz) {
    LOGV("崩溃前");
    willCrash();
    LOGV("崩溃后");
    printf("oooo");
}